
/****************************************************************************/
/*Copyright (c) 2011, Florent DEVILLE.                                      */
/*All rights reserved.                                                      */
/*                                                                          */
/*Redistribution and use in source and binary forms, with or without        */
/*modification, are permitted provided that the following conditions        */
/*are met:                                                                  */
/*                                                                          */
/* - Redistributions of source code must retain the above copyright         */
/*notice, this list of conditions and the following disclaimer.             */
/* - Redistributions in binary form must reproduce the above                */
/*copyright notice, this list of conditions and the following               */
/*disclaimer in the documentation and/or other materials provided           */
/*with the distribution.                                                    */
/* - The names of its contributors cannot be used to endorse or promote     */
/*products derived from this software without specific prior written        */
/*permission.                                                               */
/* - The source code cannot be used for commercial purposes without         */ 
/*its contributors' permission.                                             */
/*                                                                          */
/*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       */
/*"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         */
/*LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS         */
/*FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE            */
/*COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       */
/*INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,      */
/*BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;          */
/*LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER          */
/*CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT        */
/*LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN         */
/*ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
/*POSSIBILITY OF SUCH DAMAGE.                                               */
/****************************************************************************/

#include "RTPrimCylinder.h"


#include <math.h>

namespace RT
{
	/*Constructor*/
	RTPrimCylinder::RTPrimCylinder(F32 radius, F32 height):RTIPrimitive(),
		m_radius(radius), m_height(height)
	{}

	/*Destructor*/
	RTPrimCylinder::~RTPrimCylinder(){}

	/*Calculate the intersection between a ray and the cylinder*/
	bool RTPrimCylinder::intersect(const RTRay& ray, F32& dist, bool useGlobalCoordinates)
	{
		//change the coordinate system
		RTRay R;
		if (useGlobalCoordinates)
			globalToLocal(ray, R);
		else
			convertRayToLocalCoordinates(ray, R);

		//calcul of delta
		F32 b = 2 * (R.getDirection().x * R.getOrigin().x + R.getDirection().z * R.getOrigin().z);
		F32 bSquared = b * b;

		F32 a = R.getDirection().x * R.getDirection().x + R.getDirection().z * R.getDirection().z;
		F32 c = R.getOrigin().x * R.getOrigin().x + R.getOrigin().z * R.getOrigin().z - m_radius * m_radius;

		F32 delta = bSquared - 4 * a * c;

		F32 t = 0;

		//if the ray hit the cylinder with two intersection
		if(delta > 0)
		{
			//calculate the solutions
			F32 sqrtDelta = sqrt(delta);
			F32 t1 = (-b + sqrtDelta)/(2*a);
			F32 t2 = (-b - sqrtDelta)/(2*a);
		
			I32 temp = (I32)(t1 * 100);
			t1 = (F32)temp / 100;
			temp = (I32)(t2 * 100);
			t2 = (F32)temp / 100;

			//calculate the z coordinates of the intersection poI32
			F32 heightT1 = R.getOrigin().y + t1 * R.getDirection().y;
			F32 heightT2 = R.getOrigin().y + t2 * R.getDirection().y;
	
			bool useEnds = false;
			if(isInside(ray.getOrigin()))
			{
				//top end
				if ((heightT1 > m_height && heightT2 < m_height && t1 > t2) ||
					(heightT2 > m_height && heightT1 < m_height && t2 > t1))
				{
					useEnds = true;
					t = calculateTTopEnd(R);
				}

				//bottom end
				else if((heightT1 > 0 && heightT2 < 0 && t1 < t2) || (heightT2 > 0 && heightT1 < 0 && t2 < t1))
				{
					useEnds = true;
					t = calculateTBottomEnd(R);
				}
			}
			else
			{
				//top end
				if ((heightT1 > m_height && heightT2 < m_height && t1 < t2) ||
					(heightT2 > m_height && heightT1 < m_height && t2 < t1))
				{
					useEnds = true;
					t = calculateTTopEnd(R);
				}

				//bottom end
				else if((heightT1 > 0 && heightT2 < 0 && t1 > t2) || (heightT2 > 0 && heightT1 < 0 && t2 > t1))
				{
					useEnds = true;
					t = calculateTBottomEnd(R);
				}
			}

			if(useEnds){}
			//body of the cylinder
			else if(isValideHeight(heightT1) || isValideHeight(heightT2))
			{
				if(isValideHeight(heightT1) && isValideHeight(heightT2))
				{
					if(t1 <= 0 && t2 <= 0)
						return false;
					else if(t1 <= 0)
						t = t2;
					else if(t2 <= 0)
						t = t1;
					else
						t = fmin(t1, t2);
				}
				else if(isValideHeight(heightT1))
					t = t1;
				else 
					t = t2;
			}
			//no intersection
			else return false;

			//set dist
			dist = t;

			//if dist negatif, no intersection
			if (dist< 0)
				return false;

			return true;

		}
		else if (delta == 0) // tangent
		{
			t = -b/(2 * a);
		}
		else //delta <0 so it's one of the ended. The ray is aligned with the height
		{
			F32 aligned = R.getOrigin().x  * R.getOrigin().x + R.getOrigin().z * R.getOrigin().z;

			//ray aligned with the cylinder
			if(aligned < m_radius*m_radius)
			{
				bool ret = calculateTFromHeight(R, &t);
				if(!ret) return ret;
			}
			else
				return false;
		}

		dist = t;
		return true;

	}

	/*Calculate the cylinder normal*/
	void RTPrimCylinder::computeNormal(const RTRay& ray, F32 dist, RTVector3f& normal)const
	{
		//change the coordinate system
		RTRay R;
		convertRayToLocalCoordinates(ray, R);

		RTVector3f intersection = R.getOrigin() + R.getDirection() * dist;

		if(intersection.y < 0.001f && intersection.y > -0.001f)
			intersection.y = 0;

		if(intersection.y - 0.001f > 0 && intersection.y + 0.001f < m_height)
		{
			normal = intersection;
			normal.y = 0;
			normal.normalize();
		}
		else if(intersection.y <= 0)
			normal = RTVector3f(0, -1, 0);
		else
			normal = RTVector3f(0, 1, 0);

		//if ray from the inside
		if(R.getDirection().dot(normal) > 0)
			normal = normal * -1;

		//reset to the global coordinate systeme
		convertNormalToGlobalCoordinates(normal, normal);
	}

	/*Check if the point is inside the cylinder*/
	bool RTPrimCylinder::isInside(const RTVector3f& p)const
	{
		//convert p to local coordinates
		RTVector3f localP;
		convertPointToLocalCoordinates(p, localP);

		//check if valid height
		if(!isValideHeight(localP.y))
			return false;

		//check if inside the circle
		if(localP.x * localP.x + localP.z * localP.z < m_radius * m_radius)
			return true;

		return false;
	}

	/*Check if the height is valid*/
	bool RTPrimCylinder::isValideHeight(F32 h)const
	{
		if(h >= 0 && h <= m_height)
			return true;

		return false;
	}

	/*Calculate the intersection between the ray and the cylinder*/
	bool RTPrimCylinder::calculateTFromHeight(const RTRay& ray, F32* t)const
	{
		F32 t1 = (-ray.getOrigin().y)/ray.getDirection().y;
		F32 t2 = (m_height - ray.getOrigin().y)/ray.getDirection().y;

		I32 temp = (I32)(t2 * 100);
		t2 = (F32)temp/100;
		temp = (I32)(t1 * 100);
		t1 = (F32)temp/100;

		if(t1 < t2)
		{
			if(t1 > 0)
				*t = t1;
			else if(t2 > 0)
				*t = t2;
			else
				return false;
		}
		else // t2<t1
		{
			if(t2 > 0)
				*t = t2;
			else if (t1> 0)
				*t = t1;
			else
				return false;
		}
		return true;
	}

	/*Calculate the interesection between the ray and the top cap*/
	F32 RTPrimCylinder::calculateTTopEnd(const RTRay& R)const
	{
		return (m_height - R.getOrigin().y)/R.getDirection().y;
	}

	/*Calculate the interesection between the ray and the bottom cap*/
	F32 RTPrimCylinder::calculateTBottomEnd(const RTRay& R)const
	{
		return (0 - R.getOrigin().y)/R.getDirection().y;
	}

	/*Return the minimum between f1 and f2*/
	F32 RTPrimCylinder::fmin(F32 f1, F32 f2)const
	{
		if(f1 < f2)
			return f1;

		return f2;
	}

}